package failover

import (
	"JK-Junior-Go-Engineer-Camp/webook/internal/service/sms"
	"context"
	"sync/atomic"
)

// TimeoutFailOverSMSService 短息服务的 failover 装饰器（方案二）
// 连续 N 个超时就切换
type TimeoutFailOverSMSService struct {
	svcs      []sms.Service // 服务商
	idx       int32         // 当前服务商下标
	cnt       int32         // 连续几个超时了
	threshold int32         // 阈值（只读，所以无需考虑线程安全问题）
}

func NewTimeoutFailOverSMSService(svcs []sms.Service,
	threshold int32) *TimeoutFailOverSMSService {
	return &TimeoutFailOverSMSService{
		svcs:      svcs,
		threshold: threshold,
	}
}

func (t TimeoutFailOverSMSService) Send(ctx context.Context,
	templateId string, args []string, number ...string) error {
	idx := atomic.LoadInt32(&t.idx)
	cnt := atomic.LoadInt32(&t.cnt)
	length := int32(len(t.svcs))
	// 超时请求数达到阈值，执行切换
	if cnt >= t.threshold {
		newIdx := (idx + 1) % length
		// 有可能多个 goroutine 进来，但是只需要保证一个切换成功就行，所以需要CAS操作
		// CAS操作失败，说明已经被其他goroutine切换了
		if atomic.CompareAndSwapInt32(&t.idx, idx, newIdx) {
			// 重置计数器
			atomic.StoreInt32(&t.cnt, 0)
		}
		idx = newIdx
	}
	// 发短信
	svc := t.svcs[idx]
	err := svc.Send(ctx, templateId, args, number...)
	switch err {
	case nil:
		// 一旦成功，计数器归零
		atomic.StoreInt32(&t.cnt, 0)
		return nil
	case context.DeadlineExceeded: // 超时
		atomic.AddInt32(&t.cnt, 1)
	default: // 遇到了错误，但不是超时错误,你要考虑怎么搞
		// 我可以cnt+1,也可以不加
		// 如果强调一定是超时，就不加
		// 如果是 EOF之类的错误，你还可以考虑直接切换
	}
	return err
}
